﻿namespace USElections.Model.JSON
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Runtime.Serialization;
    using USElections.Model;

    /// <summary>
    /// States class.
    /// </summary>
    [DataContract(Name = "states")]
    public class States : IEnumerable<State>
    {
        #region Fields
        private static Dictionary<string, int> electoralvotes;
        private static Dictionary<string, string> statefullnames;
        #endregion

        #region Constructor
        #endregion

        #region Properties
        /// <summary>
        /// Gets the Electoral votes count per state.
        /// </summary>
        public static Dictionary<string, int> ElectoralVotes
        {
            get
            {
                if (electoralvotes == null)
                {
                    electoralvotes = PopulateElectoralVotes();
                }

                return electoralvotes;
            }
        }

        /// <summary>
        /// Gets the list of states with their fullname.
        /// </summary>
        public static Dictionary<string, string> StatesWithFullNames
        {
            get
            {
                if (statefullnames == null)
                {
                    statefullnames = PopulateStates();
                }

                return statefullnames;
            }
        }

        /// <summary>
        /// Gets or sets the States.
        /// </summary>
        [DataMember(Name = "states")]
        public Collection<State> Items { get; set; }

        /// <summary>
        /// Gets a state based on the name (code).
        /// </summary>
        /// <param name="name">The state name (code).</param>
        /// <returns>The State object or null.</returns>
        public State this[string name]
        {
            get
            {
                if (name.Length != 2)
                {
                    throw new ArgumentException("name invalid length.");
                }

                foreach (State state in this.Items)
                {
                    if (string.Compare(name, state.Name, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        return state;
                    }
                }

                return null;
            }
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Get a states full name by the state code.
        /// </summary>
        /// <param name="code">The state code.</param>
        /// <returns>The full name of the state.</returns>
        public static string GetStateFullnameByCode(string code)
        {
            if (States.StatesWithFullNames.ContainsKey(code))
            {
                return States.StatesWithFullNames[code];
            }

            return string.Empty;
        }

        /// <summary>
        /// Gets the enumerator.
        /// </summary>
        /// <returns>The ienumerator.</returns>
        public IEnumerator<State> GetEnumerator()
        {
            return this.Items.GetEnumerator();
        }

        /// <summary>
        /// Gets the enumerator.
        /// </summary>
        /// <returns>The ienumerator.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.Items.GetEnumerator();
        }

        /// <summary>
        /// Get the total electoral vote count per party.
        /// </summary>
        /// <returns>The total electoral vote count per party.</returns>
        public Dictionary<Candidate.Parties, int> TotalElectoralVoteCount()
        {
            Dictionary<Candidate.Parties, int> totalCount = new Dictionary<Candidate.Parties, int>();

            if (this.Items != null)
            {
                foreach (JSON.State state in this.Items)
                {
                    Candidate.Parties candidateParty = state.GetLeadingParty();

                    if (candidateParty != Candidate.Parties.None && candidateParty != Candidate.Parties.Undecided)
                    {
                        if (totalCount.ContainsKey(candidateParty))
                        {
                            try
                            {
                                totalCount[candidateParty] += ElectoralVotes[state.Name];
                            }
                            catch
                            {
                            }
                        }
                        else
                        {
                            try
                            {
                                totalCount.Add(candidateParty, ElectoralVotes[state.Name]);
                            }
                            catch
                            {
                            }
                        }
                    }
                }
            }

            return totalCount;
        }

        /// <summary>
        /// Get the total popular vote count per Candidate.
        /// </summary>
        /// <returns>The total popular vote count per Candidate.</returns>
        public Dictionary<Candidate, int> TotalPopularVoteCount()
        {
            Dictionary<Candidate, int> totalCount = new Dictionary<Candidate, int>();

            Candidate otherCandidates = new Candidate() { Id = -1, FullName = "Other candidates", Party = Candidate.Parties.Independent, ImageUri = string.Empty, TwitterAccount = string.Empty, TwitterId = -1 };

            if (this.Items != null)
            {
                foreach (JSON.State state in this.Items)
                {
                    if (state.Name.Contains("XX"))
                    {
                        continue;
                    }

                    foreach (Votes vote in state.Votes)
                    {
                        Candidate candidate = Data.Instance.Candidates.GetCandidateById(vote.CandidateId);

                        if (candidate != null)
                        {
                            if (candidate.Id != 1 && candidate.Id != 10)
                            {
                                candidate = otherCandidates;
                            }

                            if (totalCount.ContainsKey(candidate))
                            {
                                try
                                {
                                    totalCount[candidate] += vote.NumberOfVotes;
                                }
                                catch
                                {
                                }
                            }
                            else
                            {
                                try
                                {
                                    totalCount.Add(candidate, vote.NumberOfVotes);
                                }
                                catch
                                {
                                }
                            }
                        }
                    }
                }
            }

            return totalCount;
        }

        /// <summary>
        /// This returns the sum of the electoral votes of a candidate.
        /// This dictionary will only contain candidates that have a majority in a state and therefor an electoral vote.
        /// </summary>
        /// <returns>The list of electoral votes per candidate.</returns>
        public Dictionary<Candidate, int> ElectoralVotesPerCandidate()
        {
            Dictionary<Candidate, int> result = new Dictionary<Candidate, int>();

            if (this.Items != null)
            {
                foreach (JSON.State state in this.Items)
                {
                    Candidate candidate = state.GetLeadingCandidate();

                    if (candidate != null)
                    {
                        if (!result.ContainsKey(candidate))
                        {
                            result.Add(candidate, 0);
                        }

                        try
                        {
                            result[candidate] += ElectoralVotes[state.Name];
                        }
                        catch
                        {
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Get the total number of votes per state.
        /// </summary>
        /// <returns>Returns a dictionary with the total number of votes per state.</returns>
        public Dictionary<State, int> TotalNumberOfVotesPerState()
        {
            Dictionary<State, int> totalCount = new Dictionary<State, int>();

            if (this.Items != null)
            {
                foreach (JSON.State state in this.Items)
                {
                    int sum = state.Votes.Sum(x => x.NumberOfVotes);

                    if (totalCount.ContainsKey(state))
                    {
                        try
                        {
                            totalCount[state] += sum;
                        }
                        catch
                        {
                        }
                    }
                    else
                    {
                        try
                        {
                            totalCount.Add(state, sum);
                        }
                        catch
                        {
                        }
                    }
                }
            }

            return totalCount;
        }
        #endregion

        #region Private methods
        public static Dictionary<string, int> PopulateElectoralVotes()
        {
            Dictionary<string, int> result = new Dictionary<string, int>();

            result.Add("AL", 9);
            result.Add("AK", 3);
            result.Add("AZ", 11);
            result.Add("AR", 6);
            result.Add("CA", 55);
            result.Add("CO", 9);
            result.Add("CT", 7);
            result.Add("DE", 3);
            result.Add("FL", 29);
            result.Add("GA", 16);
            result.Add("HI", 4);
            result.Add("ID", 4);
            result.Add("IL", 20);

            result.Add("IN", 11);
            result.Add("IA", 6);
            result.Add("KS", 6);
            result.Add("KY", 8);
            result.Add("LA", 8);
            result.Add("ME", 4);
            result.Add("MD", 10);
            result.Add("MA", 11);
            result.Add("MI", 16);
            result.Add("MN", 10);
            result.Add("MS", 6);
            result.Add("MO", 10);
            result.Add("MT", 3);

            result.Add("NE", 5);
            result.Add("NV", 6);
            result.Add("NH", 4);
            result.Add("NJ", 14);
            result.Add("NM", 5);
            result.Add("NY", 29);
            result.Add("NC", 15);
            result.Add("ND", 3);
            result.Add("OH", 18);
            result.Add("OK", 7);
            result.Add("OR", 7);
            result.Add("PA", 20);
            result.Add("RI", 4);

            result.Add("SC", 9);
            result.Add("SD", 3);
            result.Add("TN", 11);
            result.Add("TX", 38);
            result.Add("UT", 6);
            result.Add("VT", 3);
            result.Add("VA", 13);
            result.Add("WA", 12);
            result.Add("WV", 5);
            result.Add("WI", 10);
            result.Add("WY", 3);

            result.Add("XX", 0); // voters from outside the US.

            result.Add("WashingtonDC", 3); // Washington DC, not a state, but allowed electoral votes.

            var count = result.Sum(x => x.Value);

            if (count != 538)
            {
                throw new Exception("Invalid number of electoral votes");
            }

            return result;
        }

        private static Dictionary<string, string> PopulateStates()
        {
            Dictionary<string, string> result = new Dictionary<string, string>();

            result.Add("AL", "Alabama");
            result.Add("AK", "Alaska");
            result.Add("AZ", "Arizona");
            result.Add("AR", "Arkansas");
            result.Add("CA", "California");
            result.Add("CO", "Colorado");
            result.Add("CT", "Connecticut");
            result.Add("DE", "Delaware");
            result.Add("FL", "Florida");
            result.Add("GA", "Georgia");
            result.Add("HI", "Hawaii");
            result.Add("ID", "Idaho");
            result.Add("IL", "Illinois");
            result.Add("IN", "Indiana");
            result.Add("IA", "Iowa");
            result.Add("KS", "Kansas");
            result.Add("KY", "Kentucky");
            result.Add("LA", "Louisiana");
            result.Add("ME", "Maine");
            result.Add("MD", "Maryland");
            result.Add("MA", "Massachusetts");
            result.Add("MI", "Michigan");
            result.Add("MN", "Minnesota");
            result.Add("MS", "Mississippi");
            result.Add("MO", "Missouri");
            result.Add("MT", "Montana");
            result.Add("NE", "Nebraska");
            result.Add("NV", "Nevada");
            result.Add("NH", "New Hampshire");
            result.Add("NJ", "New Jersey");
            result.Add("NM", "New Mexico");
            result.Add("NY", "New York");
            result.Add("NC", "North Carolina");
            result.Add("ND", "North Dakota");
            result.Add("OH", "Ohio");
            result.Add("OK", "Oklahoma");
            result.Add("OR", "Oregon");
            result.Add("PA", "Pennsylvania");
            result.Add("RI", "Rhode Island");
            result.Add("SC", "South Carolina");
            result.Add("SD", "South Dakota");
            result.Add("TN", "Tennessee");
            result.Add("TX", "Texas");
            result.Add("UT", "Utah");
            result.Add("VT", "Vermont");
            result.Add("VA", "Virginia");
            result.Add("WA", "Washington");
            result.Add("WV", "West Virginia");
            result.Add("WI", "Wisconsin");
            result.Add("WY", "Wyoming");

            result.Add("XX", "Foreign votes");

            return result;
        }
        #endregion
    }
}
